﻿using System;
using System.Reflection;

namespace Framework.Domain
{
    public abstract class Identity<T> : IEquatable<Identity<T>> where T : IEquatable<T>, new()
    {
        private readonly T _value;

        protected Identity()
        {
            // this should cause some kind of exception if an entity gets committed with the default value.
            _value = new T();
        }

        protected Identity(T id)
        {
            _value = id;
        }

        #region Equality overrides

        // This is the the one equality rule for this object
        // All the other equality checks derive from this and mostly cover 
        // null reference and type conversion behaviour
        private static bool EqualsImpl(Identity<T> a, Identity<T> b)
        {
            // no null checks

            //TO-DO:  This IS be dodgy - need to compare actual values not hashcode
            // because there could be duplicates across subclasses
            return a.GetHashCode() == b.GetHashCode();
        }

        public static bool Equals(Identity<T> a, Identity<T> b)
        {
            // If both are null, or both are same instance, return true.
            if (ReferenceEquals(a, b)) return true;

            // If one is null, but not both, return false.
            if (((Object)a == null) || ((Object)b == null)) return false;

            return EqualsImpl(a, b);
        }

        public bool Equals(Identity<T> id)
        {
            return Equals(this, id);
        }

        public override bool Equals(Object obj)
        {
            // If parameter is null return false.
            if (obj == null) return false;

            // If parameter cannot be cast to Identity return false.
            var id = obj as Identity<T>;
            if ((Object)id == null) return false;

            return EqualsImpl(this, id);
        }

        public static bool operator ==(Identity<T> a, Identity<T> b)
        {
            return Equals(a, b);
        }

        public static bool operator !=(Identity<T> a, Identity<T> b)
        {
            // Derivative compliment of operator ==
            return !(a == b);
        }

        #endregion

        #region Other System.Object overrides

        public override int GetHashCode()
        {
            return this.GetType().GetHashCode() ^ _value.GetHashCode();
        }

        public override string ToString()
        {
            return string.Format("{0}:{1}", GetType().Name, _value);
        }

        #endregion

        public static T GetValue(Identity<T> identity)
        {
            FieldInfo valueFieldInfo = typeof(Identity<T>).GetField("value", BindingFlags.NonPublic | BindingFlags.Instance);
            if (identity == null)
            {
                //return default(T);
                throw new ArgumentNullException("identity", "Identity cannot be null");
            }
            return (T)valueFieldInfo.GetValue(identity);
        }

        public T Value
        {
            get { return GetValue(this); }
        }
    }
}